package com.glodon.paas.storage.fs;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.css.DocumentCSS;

import com.glodon.paas.exception.PaasServerExceptionFactory;
import com.glodon.paas.storage.AbstractBulkStorage;
import com.glodon.paas.storage.InitResult;
import com.glodon.paas.storage.Part;
import com.glodon.paas.storage.ReadResult;
import com.glodon.paas.storage.WriteResult;

/**
 * A file system based implementation of BulkStorage
 * @author guojf
 *
 */
public class FSBulkStorage extends AbstractBulkStorage {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(FSBulkStorage.class);

	private String root;
	
	public FSBulkStorage(String root) {
		this.root = root;
	}
	
	@Override
	public WriteResult write(String key, InputStream object, long length){
		String path = FilenameUtils.concat(root, key);
		String dir = FilenameUtils.getPrefix(path) + FilenameUtils.getPath(path);
		FileOutputStream fos=null;
		try {
			FileUtils.forceMkdir(new File(dir));
			fos = new FileOutputStream(path);
			IOUtils.copy(object, fos);
		} catch (IOException er) {
			throw PaasServerExceptionFactory.storageException("failed to write object to file system: KEY = " + key, er);
		} finally{
			try {
				object.close();
				fos.close();
			} catch (IOException ignored) {
			}
		}
		WriteResult wr = new WriteResult();
		wr.setKey(key);
		return wr;
	}

	@Override
	public ReadResult read(String key) {
		String path = FilenameUtils.concat(root, key);
		try {
			File f = new File(path);
			ReadResult response = new ReadResult(FileUtils.openInputStream(f));
			response.setLength(f.length());
			response.setName(f.getName());
			return response;
		} catch (IOException er) {
			throw PaasServerExceptionFactory.storageException("failed to read object from file system: KEY = " + key, er);
		}
	}

	@Override
	public void delete(String key) {
		String path = FilenameUtils.concat(root, key); 
		try {
			File f = new File(path);
			FileUtils.forceDelete(f);
		} catch (IOException er) {
			throw PaasServerExceptionFactory.storageException("failed to remove object from file system: KEY = " + key, er);
		}
		
	}
	
	@Override
	public boolean isPartialReadSupported() {
		return true;
	}
	
	@Override
	public ReadResult read(String key, long start, long end) {
		String path = FilenameUtils.concat(root, key);
		try {
			RandomAccessFile f = new RandomAccessFile(path, "r");
			ReadResult response = new ReadResult(Channels.newInputStream(f.getChannel().position(start)));
			response.setLength(f.length());
			return response;
		} catch (IOException er) {
			throw PaasServerExceptionFactory.storageException("failed to read object from file system: KEY = " + key, er);
		}
	}
	
	@Override
	public boolean isMultiPartUploadSupported(){
		return true;
	}
	
	@Override
	public InitResult initMultiPartUpload(String key) {
		try {
			String path = FilenameUtils.concat(root, key);
			String dir = FilenameUtils.getPrefix(path) + FilenameUtils.getPath(path);
			FileUtils.forceMkdir(new File(dir));
//			new File(path).createNewFile();
			return new InitResult(key);
		} catch (Exception er) {
			throw PaasServerExceptionFactory.storageException("failed to initiate multipart Upload object to FS: KEY = " + key, er);
		}
	}
	
	@Override
	public void abortMultiPartUpload(InitResult handle){
		String path = FilenameUtils.concat(root, handle.getKey()); 
		try {
			File f = new File(path+"part1");
			for(int i=2; f.exists(); i++){
				f.delete();
				f = new File(path+"part"+i);
			}
		} catch (Exception er) {
			throw PaasServerExceptionFactory.storageException("failed to remove object from file system: KEY = " + handle.getKey(), er);
		}
	}
	
	@Override
	public WriteResult uploadPart(InitResult handle, Part part, InputStream is){
		String path = FilenameUtils.concat(root, handle.getKey());
		FileOutputStream fos = null;
		FileInputStream fis = null;
		File file = new File(path+"part"+part.getIndex());
		try {
			fos = new FileOutputStream(file);
			IOUtils.copy(is, fos);
			fis = new FileInputStream(file);
			if(fis.available() != part.getSize()){
				throw new IOException("failed to write object to file system: KEY = " + handle.getKey(), null);
			}
			
		} catch (IOException er) {
			if(file.delete()){
				LOGGER.debug("delete part file["+path+"part"+part.getIndex()+"]");
			}
			throw PaasServerExceptionFactory.storageException("failed to write object to file system: KEY = " + handle.getKey(), er);
		}
		finally{
			try {
				if(fis != null){
					fis.close();
				}
				if(fos != null){
					fos.close();
				}
			} catch (IOException ignored) {
			}
		}
		
		
		WriteResult wr = new WriteResult();
		wr.setKey(handle.getKey());
		return wr;
	}

	
	@Override
	public WriteResult completeMultiPartUpload(InitResult handle, List<Part> parts) {
		String path = FilenameUtils.concat(root, handle.getKey());
		FileOutputStream fos = null;
		FileInputStream fis = null;
		File file = null;
		try {
			fos = new FileOutputStream(path);
			for(Part part : parts){
				file = new File(path+"part"+part.getIndex());
				fis = FileUtils.openInputStream(file);
				IOUtils.copy(fis, fos);
				IOUtils.closeQuietly(fis);
				FileUtils.deleteQuietly(file);
				file.delete();
			}
		} catch (IOException er) {
			throw PaasServerExceptionFactory.storageException("failed to write object to file system: KEY = " + handle.getKey(), er);
		} finally{
			try {
				if(fos != null){
					fos.close();
				}
			} catch (IOException ignored) {
			}
		}
		
		WriteResult wr = new WriteResult();
		wr.setKey(handle.getKey());
		return wr;
	}
}
